#include "preloader.h"

#define PRELOADER_VERSION "preloader V1.0, Copyright (C) 2012 ChinaCache"

static const char helper_info[] = 
"\t-h, --help	print the help massage\n"
"\t-p, --port	listen port\n"
;

typesize_t tsize;
preloader_t preloader;
static xmlDocPtr doc;
static xmlNodePtr cur;
static int refreshd_port = 21108;
static char devid[BUF_SIZE_128];
static char store_dir[] = "/tmp/preloader";
static char refreshd_ip[BUF_SIZE_16] = "127.0.0.1";
static char refreshClient[] = "/usr/local/squid/bin/refresh_cli";
//static char squidClient[] = "/usr/local/squid/bin/squidclient";

static char *xcalloc(size_t n, size_t sz)
{
	void *ptr;

	ptr = calloc(n, sz);
	if (NULL == ptr)
	{
		plog(ERROR, "xcalloc failed, exiting now, ERROR=[errno]", strerror(errno));
		exit(1);
	}
	return ptr; 	
}

static char *xstrdup(const char *src)
{
	void *dst;
	size_t size;

	if (NULL == src)
	{
		plog(ERROR, "xstrcpy(NULL), usage error, exiting now");
		exit(1);
	}
	size = strlen(src) + 1;
	dst = xcalloc(size, 1);

	return memcpy(dst, src, size);
}

static void getDevId(void)
{
	FILE *fp = NULL; 

	fp = fopen("/sn.txt", "r");
	if (NULL == fp)
	{
		plog(ERROR, "fopen(\"/sn.txt\") failed[%s]", strerror(errno));
		exit(1);
	}
	memset(devid, 0, BUF_SIZE_128);
	fgets(devid, BUF_SIZE_128, fp);
	devid[strlen(devid) - 1] = '\0'; 
	fclose(fp);
}

static int checkDirectory(const char *dir)
{
	struct stat st; 

	if (stat(dir, &st) < 0)
	{
		if (mkdir(dir, 0755) < 0)
		{       
			fprintf(stderr, "mkdir() failed, DIR=[%s], ERROR=[%s]", dir, strerror(errno));
			return -1;
		}
	}

	return 0;
}

static int packSendData(task_t *task, url_node_t *node)
{
	void *ptr = NULL;
	size_t datalen, size;
	char result[BUF_SIZE_512];

	memset(result, 0, BUF_SIZE_512);
	snprintf(result, BUF_SIZE_512,
			"    <url_ret id=\"%d\">%s</url_ret>\n", node->id, node->code);
	/*
	   snprintf(xml_buffer, BUF_SIZE_4096,
	   "<url_ret id=\"%d\">%s,%d,%s</url_ret>\n",
	   node->id, node->code, node->status, node->md5);
	 */
	if (NULL == task->buf)
		plog(COMMON, "strlen(task->buf): 0, task->buflen: %d, url report data--> %s", task->buflen, result);
	else
		plog(COMMON, "strlen(task->buf): %d, task->buflen: %d, url report data--> %s", strlen(task->buf), task->buflen, result);
	datalen = strlen(result);
	size = task->buflen + datalen + 1;
	ptr = (NULL == task->buf) ? xcalloc(size, 1) : realloc(task->buf, size);
	if (NULL == ptr)
	{
		plog(ERROR, "xalloc() or realloc() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	task->buf = ptr;
	memcpy(task->buf + task->buflen, result, datalen + 1);
	task->buflen = strlen(task->buf);
	plog(COMMON, "data after pack-->\n%s", task->buf);

	return 0;
}

static int postResultToServer(task_t *task)
{
	ssize_t ret = send(task->sockfd, task->buf, task->buflen, 0);
	if (ret < 0)
	{
		fprintf(stderr, "send() failed, ERROR=[%s]\n", strerror(errno));
		return -1;
	}
	else if (0 == ret) 
	{
		plog(WARNING, "send() error, ERROR=[%s]\n", strerror(errno));
		return -1;
	}
	else    
	{
		plog(COMMON, "send() succeed, SENDLEN=[%ld], DATA:\n%s\n", ret, task->buf);
	}

	return 0;
}

static int purgeCacheObj(const int type, const char *url, const int action)
{
	char commbuf[BUF_SIZE_4096];

	memset(commbuf, 0, BUF_SIZE_4096);
	//snprintf(commbuf, BUF_SIZE_4096, "/usr/local/squid/bin/squidclient -p 80 -m PURGE %s", url);
	switch (type)
	{
		case ACTION_URL:
			snprintf(commbuf, BUF_SIZE_4096,
					"%s \"%s\" 1> /dev/null 2>&1",
					refreshClient, url);
			break;
		case ACTION_DIR:
			snprintf(commbuf, BUF_SIZE_4096,
					"%s -s %d -d \"%s\" 1> /dev/null 2>&1",
					refreshClient, action, url);
		default:
			plog(ERROR, "no this type, TYPE=[%d]", type);
			return -1;
	}
	system(commbuf);

	return 0;
}

static void getResponseCode(char *path, char *code)
{
	char *str = NULL;
	char *saveptr = NULL;
	char line[BUF_SIZE_1024];
	FILE *fp = NULL;

	fp = fopen(path, "r");

	while (!feof(fp)) {
		memset(line, 0, BUF_SIZE_1024);
		if (NULL == fgets(line, 1024, fp))
			continue;
		if (NULL != strstr(line, "HTTP/1."))
		{
			str = strtok_r(line, " :\t\n", &saveptr);		
			if (NULL == str) {
				continue;
			} 
			str = strtok_r(NULL, " \n", &saveptr);
			if (NULL == str)
				continue;
			//memset(code, 0, sizeof(code));
			strncpy(code, str, 3);
			code[3] = '\0';
			plog(COMMON, "wget CODE: %s", str);
			break;
		}
	}
	fclose(fp);
	if ('\0' == code[0])
		strcat(code, "000");
}

static int urlPreloadOne(task_t *task, url_node_t *node)
{
	int i, type;
	FILE *fp = NULL;
	char path[BUF_SIZE_256];
	char res_path[BUF_SIZE_256];
	char cmd[BUF_SIZE_1024];
	char commbuf[BUF_SIZE_4096];
	//char xml_buffer[BUF_SIZE_4096];

	//purge any cache object in tasklist, and download again 
	switch (task->method)
	{
		case ACTION_URL_PURGE:
		case ACTION_URL_EXPIRE:
		case ACTION_URL_PRELOAD:
			type = ACTION_URL;
			break;
		case ACTION_DIR_PURGE:
		case ACTION_DIR_EXPIRE:
		case ACTION_DIR_PRELOAD:
			type = ACTION_DIR;
			break;
		default:
			plog(ERROR, "no this method, METHOD=[%d]", task->method);
			return -1;
	}
	purgeCacheObj(type, node->url, task->action);
	memset(commbuf, 0, BUF_SIZE_4096);
	snprintf(commbuf, BUF_SIZE_4096,
			"/usr/bin/wget --timeout=3 --tries=1 -O /dev/null -S --header=\"Accept-Encoding: gzip,deflate\" -e http_proxy=127.0.0.1 \"%s\"", node->url);
	system(commbuf);
	memset(commbuf, 0, BUF_SIZE_4096);
	memset(res_path, 0, BUF_SIZE_256);
	memset(path, 0, BUF_SIZE_256);
	snprintf(path, BUF_SIZE_256, "%s/file.tmp", store_dir);
	snprintf(res_path, BUF_SIZE_256, "%s/header.tmp", store_dir);
	snprintf(commbuf, BUF_SIZE_4096,
			"/usr/bin/wget --timeout=3 --tries=1 -O %s -S -o %s -e http_proxy=127.0.0.1 \"%s\"", 
			path, res_path, node->url);
	system(commbuf);
	getResponseCode(res_path, node->code);
	unlink(res_path);
	if (strcmp(node->code, "200"))
	{
		unlink(path);
		node->status = 0;
		goto RET;
	}
	memset(cmd, 0, BUF_SIZE_1024);
	snprintf(cmd, BUF_SIZE_1024, "/usr/bin/md5sum %s", path);
	fp = popen(cmd, "r");
	fgets(node->md5, BUF_SIZE_64, fp);
	pclose(fp);
	unlink(path);
	for (i = 0; i < BUF_SIZE_64; ++i)
	{
		if (isalpha(node->md5[i]) || isdigit(node->md5[i]))
			continue;
		node->md5[i] = '\0';
		break;
	}   
	node->status = 1;

RET:
	packSendData(task, node);

	return 0;
}

static int cmp(void *key, void *data)
{
	return *((int *)key) - ((task_t *)data)->sockfd;
}

static int preloaderTasklistCreate(void)
{
	detect_obj_t obj;

	memset(&obj, 0, tsize.detect_obj_t_size);
	obj.type = THRD_NSAFE;
	obj.size = tsize.task_t_size;
	if (FAILED == detectObjCall(OBJ_INIT, &obj))
		return -1;
	preloader.tasklist = obj.llist;
	return 0;
}

static int preloaderTasklistAdd(task_t *task)
{
	detect_obj_t obj;

	memset(&obj, 0, tsize.detect_obj_t_size);
	obj.type = TAIL;
	obj.obj = task;
	obj.llist = preloader.tasklist;
	if (FAILED == detectObjCall(OBJ_ADD, &obj))
		return -1;
	return 0;
}

static int preloaderTasklistDel(task_t *task)
{
	detect_obj_t obj;

	memset(&obj, 0, tsize.detect_obj_t_size);
	obj.obj = task;
	obj.cmp = cmp;
	obj.llist = preloader.tasklist;
	detectObjCall(OBJ_DEL, &obj);
}

static int preloaderResetRlimit(void)
{
	struct rlimit rlmt;

	getrlimit(RLIMIT_NOFILE, &rlmt); 
	if (rlmt.rlim_max < PRELOADER_FD_MAX)
	{
		rlmt.rlim_max = PRELOADER_FD_MAX; 
		rlmt.rlim_cur = rlmt.rlim_max;
	}
	if (rlmt.rlim_cur < rlmt.rlim_max)
		rlmt.rlim_cur = rlmt.rlim_max;
	if (setrlimit(RLIMIT_NOFILE, &rlmt) < 0)
	{
		return FAILED;  
	}
	plog(COMMON, "setrlimit() succeed, rlim_cur=[%ld], rlim_max=[%ld]", rlmt.rlim_cur, rlmt.rlim_max);

	return SUCC;
}

static void preloaderKillRunningPort(int port)
{
	FILE *fp = NULL;
	char cmd[BUF_SIZE_128];
	char buffer[BUF_SIZE_2048];

	snprintf(cmd, BUF_SIZE_128, "/sbin/fuser -k -9 -n tcp %d 2>&1", port);
	fp  = popen(cmd, "r");
	if(NULL == fp) 
	{
		plog(ERROR, "popen(\"%s\", \"r\") failed, ERROR=[%s]\n", strerror(errno));
		exit(1);
	}
	for ( ; ; )
	{
		if (NULL == fgets(buffer, BUF_SIZE_2048, fp))
			break;
		plog(COMMON, "%s", buffer);
	}
	pclose(fp);
}

void preloaderSafeFree(void **ptr)
{
	if (NULL == ptr || NULL == *ptr)
		return;
	free(*ptr);
	*ptr = NULL;
}

void *preloaderSafeCalloc(int size)
{
	int count = 0;
	void *buf = NULL;

	do {
		buf = calloc(size, 1);
		if (NULL == buf)
		{
			if (++count > 3)
			{
				plog(ERROR, "calloc() failed, ERROR=[%s]", strerror(errno));
				break;
			}
			usleep(1000);
		}
	} while (NULL == buf);

	return buf;
}

static void preloaderVersion(void)
{
	fprintf(stderr, "%s\n", PRELOADER_VERSION);
}

static void preloaderHelper(void)
{
	fprintf(stderr, "%s", helper_info);
}

static void preloaderParseOptions(int argc, char **argv)
{
	int opt, option_index;
	struct option long_options[] = {
		{"port",    1, NULL, 'p'},
		{"help",    0, NULL, 'h'},
		{"version", 0, NULL, 'v'},
		{0, 0, 0, 0}
	};

	do {    
		opt = getopt_long(argc, argv, "p:hv", long_options, &option_index);
		if (-1 == opt)
			break;  
		switch (opt) { 
			case 'h':
				preloaderHelper();
				exit(0);
			case 'p':
				preloader.port = atoi(optarg); 
				break;  
			case 'v':
				preloaderVersion();
				exit(0);
			case ':':
				fprintf(stderr, "option needs a value, OPTION=[%c]\n", opt);
				plog(ERROR, "option needs a value, OPTION=[%c]\n", opt);
				exit(1);
			case '?':
				fprintf(stderr, "unkown command option, OPTION=[%c]", opt);
				plog(WARNING, "unkown command option, OPTION=[%c]", opt);
				exit(1);
		}
	} while (1);
}

static void preloaderSizeFigure(void)
{
	tsize.task_t_size = sizeof(task_t);
	tsize.peek_data_t_size = sizeof(peek_data_t);
	tsize.sock_index_t_size = sizeof(sock_index_t);
	tsize.detect_obj_t_size = sizeof(detect_obj_t);
}

static void preloaderVarInit(void)
{
	memset(&preloader, 0, sizeof(preloader));
	preloader.port = LOCAL_DEFAULT_PORT;
	preloaderSizeFigure();
	preloaderTasklistCreate();
	detectEpollCall(EPOLL_INIT, 0, 0, NULL);
}

static int setSockfdNonblocking(const int sockfd) 
{
	int flags;

	flags = fcntl(sockfd, F_GETFL);
	if (flags < 0)
	{
		plog(ERROR, "fcntl(F_GETFL) failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	if (fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0)
	{
		plog(WARNING, "fcntl(F_SETFL) failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	return 0;
}

static void *preloaderFindEntry(int sockfd)
{
	detect_obj_t obj;

	obj.obj = &sockfd; 
	obj.type = ADDR; 
	obj.cmp = cmp; 
	obj.llist = preloader.tasklist;
	if (FAILED == detectObjCall(OBJ_FIND, &obj))
	{
		plog(WARNING, "can't find entry for SOCKFD: %d", sockfd);
		return NULL;
	}

	return obj.entry;
}

static int preloaderSetIndexUse(int sockfd)
{
	void *ptr = NULL;

	if (sockfd < preloader.index.max_sockfd)
	{
		if (1 == preloader.index.idx[sockfd].inuse)	
		{
			plog(WARNING, "index position have not been used");
			return -1;
		}
		preloader.index.idx[sockfd].inuse = 1;
	}
	else
	{
		ptr = realloc(preloader.index.idx, tsize.sock_index_t_size * (sockfd + 1));
		if (NULL == ptr)
		{
			plog(ERROR, "realloc() failed, ERROR=[%s]", strerror(errno));
			return -1;
		}
		preloader.index.idx = ptr;
		preloader.index.max_sockfd = sockfd;
		preloader.index.idx[sockfd].inuse = 1;
	}
	//preloader.index.idx[sockfd].timestamp = time(NULL);

	return 0;
}

static int preloaderSetIndexRelease(int sockfd)
{
	int i;

	if (sockfd > preloader.index.max_sockfd)
		return -1;
	preloader.index.idx[sockfd].inuse = 0;
	preloader.index.idx[sockfd].rfc = NULL;
	if (sockfd < preloader.index.max_sockfd)
		return 0;
	for (i = sockfd; i >= 0; --i)
	{
		if (preloader.index.idx[sockfd].inuse)
		{
			preloader.index.max_sockfd = i;
			return 0;
		}

	}
	preloader.index.max_sockfd = 0;
	//preloader.index.idx[sockfd].timestamp = time(NULL);

	return 0;
}

static int preloaderSetIndex(const int type, int sockfd)
{
	switch (type)
	{
		case USE:
			return preloaderSetIndexUse(sockfd);
		case RELEASE:
			return preloaderSetIndexRelease(sockfd);
		default:
			plog(ERROR, "no this type, TYPE=[%d]", type);
			return -1;
	}

	return 0;
}

static int preloaderSetIndexBySelf(int sockfd)
{
	task_t *task;

	preloaderSetIndex(USE, sockfd);
	task = preloaderFindEntry(sockfd);
	if (NULL == task)
		return -1;
	preloader.index.idx[sockfd].rfc = task;

	return 0;
}

static int preloaderSetIndexByOther(int sockfd, int other)
{
	task_t *task;

	preloaderSetIndex(USE, sockfd);
	task = preloaderFindEntry(other);
	if (NULL == task)
	{
		plog(ERROR, "not found client sockfd entry");
		return -1;
	}
	preloader.index.idx[sockfd].rfc = task;
	return 0;
}

static int preloaderAcceptHandle(int listen_sockfd)
{
	int sockfd;
	task_t task;
	struct sockaddr_in peeraddr;
	socklen_t addrlen = sizeof(peeraddr);

	sockfd = accept(listen_sockfd, (void *)&peeraddr, &addrlen);
	if (sockfd < 0)
	{
		plog(ERROR, "accept() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	memset(&task, 0, tsize.task_t_size);
	task.sockfd = sockfd;
	inet_ntop(AF_INET, &peeraddr, task.ip, BUF_SIZE_16);
	preloaderTasklistAdd(&task);
	preloaderSetIndexBySelf(sockfd);
	detectEpollCall(EPOLL_ADD, sockfd, EPOLLIN|EPOLLERR|EPOLLHUP, NULL);

	return 0;
}

static int checkSockfdValid(const int sockfd)
{
	int sock_errno = 0;
	socklen_t sockerr_len = sizeof(sock_errno);

	if (0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_errno, &sockerr_len))
	{
		return FALSE;
	}
	if (0 == sock_errno || EISCONN == sock_errno)
		return TRUE;

	return FALSE;
}

int parsePreloadUrlList(xmlNodePtr cur, char *token, task_t *task)
{
	char *c, *d;
	xmlChar *key, *id;
	cur = cur->xmlChildrenNode;

	int b_find = 0;

	while (NULL != cur)
	{
		if (!xmlStrcmp(cur->name, (const xmlChar *)token))
		{
			key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
			if(!(key && xmlStrlen(key)))
			{
				xmlFree(key);
				goto NEXT;
			}
			id = xmlGetProp(cur,(const xmlChar *)"id");
			if(!(id && xmlStrlen(id)))
			{
				xmlFree(id);
				xmlFree(key);
				goto NEXT;
			}
			b_find = 1;
			url_node_t *url = calloc(sizeof(url_node_t), 1);
			url->url = calloc(xmlStrlen(key) + 1, 1);
			strcpy(url->url, (char *)key);
			while(NULL != (c = strstr(url->url,"%26")))
			{
				*c++ = '&';
				d = c + 2;
				while((*d) != '\0') *c++ = *d++;
				*c = '\0';
			}
			//url->len = xmlStrlen(key);
			url->id = strtoll((const char *)id, (char **)NULL, 10);
			url->next = NULL;
			xmlFree(key);
			xmlFree(id);
			//add to url list
			if(NULL == task->urls.list)
			{
				task->urls.list = url;
				task->urls.head = task->urls.list;
				task->urls.tail = task->urls.list;
			}
			else
			{
				(task->urls.tail)->next = url;
				task->urls.tail = url;
			}
			task->urls.number++;
		}

NEXT:
		cur = cur->next;
	}

	return b_find ? 0 : -1;
}

int parsePreloadUrl(xmlNodePtr cur, task_t *task)
{
	int b_url = -1, ret = 0;

	while (NULL != cur)
	{ 
		if (!xmlStrcmp(cur->name, (const xmlChar *)"url_list"))
		{
			ret = parsePreloadUrlList(cur, "url", task);
			if(ret < 0)
			{ 
				b_url = -1;
				break;  
			}       
			b_url = 0; 
		}       
		cur = cur->next;
	}

	return b_url;
}

static int parsePreloadDir(xmlNodePtr cur, task_t *task)
{ 
	int b_action = 0;
	int b_dir = 0;

	while (NULL != cur)
	{ 
		if (!xmlStrcmp(cur->name, (const xmlChar *)"action"))
		{
			xmlChar *key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
			task->action = strtol((const char *)key, (char **)NULL, 10);
			b_action = 1; 
			xmlFree(key);
		}       
		if (!xmlStrcmp(cur->name, (const xmlChar *)"report_address"))
		{
			xmlChar *key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
			strncpy(task->report_address, (char *)key, BUF_SIZE_16); 
			xmlFree(key);
		}
		if (!xmlStrcmp(cur->name, (const xmlChar *)"dir"))
		{
			xmlChar *key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
			if(xmlStrlen(key) > strlen("http://"))
			{
				url_node_t * url = calloc(sizeof(url_node_t), 1);
				url->url = calloc(xmlStrlen(key) + 1, 1);
				strcpy(url->url,(char *)key);
				//url->len = xmlStrlen(key);
				task->urls.list = url;
				b_dir = 1;
			}
			else
			{
				plog(WARNING, "invalid url, URL=[%s]", key);
				xmlFree(key);
				return -1;
			}
			xmlFree(key);
		}
		cur = cur->next;
	}

	return (b_action && b_dir);
}

static ssize_t preloaderParseMsgHeaders(char *buffer, peek_data_t *peek)
{
	size_t contlen;
	char *pos, *token;

	pos = strstr(buffer, "\r\n\r\n");
	if (NULL == pos)
	{
		plog(ERROR, "message has no headers from peer");
		return -1;
	}
	peek->hdrlen = pos - buffer + strlen("\r\n\r\n");
	contlen = strlen("Content-Length");
	token = strtok(buffer, "\r\n");
	while (NULL != token)
	{
		if (!strncasecmp(token, "Content-Length", contlen))
		{
			pos = strchr(token, ':');
			if (NULL == pos)
			{
				plog(ERROR, "header 'Content-Length' has wrong format");
				return -1;
			}
			peek->contlen = atol(++pos);
			plog(COMMON, "Content-Length: %ld", peek->contlen);
			return (peek->contlen < 0) ? -1 : 0;
		}
		token = strtok(NULL, "\r\n");
	}

	return -1;
}

static ssize_t preloaderPeekMsgHeaders(int sockfd, peek_data_t *peek)
{
	ssize_t ret;
	char buffer[BUF_SIZE_2048];

	memset(buffer, 0, BUF_SIZE_2048);
	ret = recv(sockfd, buffer, BUF_SIZE_2048, MSG_PEEK);
	if (ret < 0)
	{
		plog(ERROR, "recv(MSG_PEEK) failed, ERROR=[%s]", strerror(errno));
		return -1;
	}

	return preloaderParseMsgHeaders(buffer, peek);
}

static int preloaderActionAtoi(char *action)
{
	if(!strcasecmp(action, "url_preload"))
		return ACTION_URL_PRELOAD;
	if(!strcasecmp(action, "url_purge"))
		return ACTION_URL_PURGE;
	if(!strcasecmp(action, "url_expire"))
		return ACTION_URL_EXPIRE;
	if(!strcasecmp(action, "dir_purge"))
		return ACTION_DIR_PURGE;
	if(!strcasecmp(action, "dir_expire"))
		return ACTION_DIR_EXPIRE;

	return ACTION_UNKNOWN;
}

static int preloaderParseMsgBody(task_t *task, peek_data_t *peek)
{
	char *bodypos;
	xmlChar *method_name , *sessid;

	bodypos = task->buffer + peek->hdrlen;

	doc = xmlParseMemory(bodypos, peek->contlen);
	if (NULL == doc)
	{
		plog(ERROR, "xmlParseMemory() failed, BUFFER=[%s]", task->buffer);
		goto OUT;
	}
	cur = xmlDocGetRootElement(doc);
	if (NULL == cur)
	{
		plog(ERROR, "xmlDocGetRootElement() failed, empty document");
		goto OUT;
	}
	if (xmlStrcmp(cur->name, (const xmlChar *) "method"))
	{
		plog(ERROR, "document has no root node 'method'");
		goto OUT;
	}
	method_name = xmlGetProp(cur,(const xmlChar *)"name");
	if (NULL == method_name)
	{
		plog(ERROR, "no method name");
		goto OUT;
	}
	sessid = xmlGetProp(cur,(const xmlChar *)"sessionid");
	if (NULL == sessid)
	{
		plog(ERROR, "no sessionid");
		xmlFree(method_name);
		goto OUT;
	}
	task->method = preloaderActionAtoi((char *)method_name);
	task->sessionid = xstrdup((char *)sessid);
	xmlFree(method_name);
	xmlFree(sessid);
	cur = cur->xmlChildrenNode;
	switch (task->method)
	{
		case ACTION_URL_PURGE:
		case ACTION_URL_EXPIRE:
		case ACTION_URL_PRELOAD:
			if (parsePreloadUrl(cur, task) < 0)
				goto OUT;
			break;
		case ACTION_DIR_PURGE:
			plog(WARNING, "Do not support dir purge!");
			goto OUT;
		case ACTION_DIR_EXPIRE:
			if (parsePreloadDir(cur, task) < 0)
				goto OUT;
			break;
		default:
			plog(ERROR, "unknown method, METHOD=[%s]", method_name);
			break;
	};
	return 0;

OUT:
	if (NULL != doc)
	{
		xmlFreeDoc(doc);
		doc = NULL;
	}
	return -1;
}

static int preloaderConnectToRefreshd(int client_sockfd)
{
	int sockfd;
	struct sockaddr_in refreshd_addr;

	memset(&refreshd_addr, 0, sizeof(struct sockaddr_in));
	refreshd_addr.sin_family = AF_INET;
	refreshd_addr.sin_port = htons(refreshd_port);
	if (inet_pton(AF_INET, refreshd_ip, &refreshd_addr.sin_addr) < 0)
	{
		plog(ERROR, "inet_pton() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		plog(ERROR, "socket() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	if (setSockfdNonblocking(sockfd) < 0)
	{
		close(sockfd);
		return -1;
	}
	if(connect(sockfd, (struct sockaddr *)&refreshd_addr, sizeof(struct sockaddr)) < 0)
	{
		if(EINPROGRESS != errno)
		{       
			plog(ERROR, "connect() failed, ERROR=[%s]", strerror(errno));
			return -1;
		}
	}
	detectEpollCall(EPOLL_ADD, sockfd, EPOLLOUT|EPOLLERR|EPOLLHUP, NULL);
	preloaderSetIndexByOther(sockfd, client_sockfd);

	return 0;
}

static int preloaderReleaseTask(task_t *task)
{
	url_node_t *cur, *next;

	if (NULL == task)
		return 0;
	if (NULL != task->buf)
		preloaderSafeFree((void *)&task->buf);
	if (NULL != task->buffer)
		preloaderSafeFree((void *)&task->buffer);
	if (NULL != task->sessionid)
		preloaderSafeFree((void *)&task->sessionid);
	for (cur = task->urls.list; NULL != cur; cur = next)
	{
		next = cur->next;
		preloaderSafeFree((void *)&cur->url);
		preloaderSafeFree((void *)&cur);
	}
	task->buflen = 0;
	task->urls.list = NULL;
	task->urls.head = NULL;
	task->urls.tail = NULL;
	detectEpollCall(EPOLL_DEL, task->sockfd, 0, NULL);
	close(task->sockfd);
	detect_obj_t obj;
	obj.obj = &task->sockfd;
	obj.cmp = cmp;
	obj.llist = preloader.tasklist;
	if (FAILED == detectObjCall(OBJ_ADD, &obj))
		return -1;
	preloaderSetIndex(RELEASE, task->sockfd);
	preloaderSafeFree((void *)&task);

	return 0;
}

static int packReportData(task_t *task)
{
	char *ptr;
	char hdr_buffer[BUF_SIZE_4096];
	char xml_end[BUF_SIZE_1024];
	char xml_begin[BUF_SIZE_1024];
	size_t total = 0, hdr_len = 0;
	size_t xmlend_len = 0, xmlbegin_len = 0;

	memset(xml_end, 0, BUF_SIZE_1024);
	//xmlend_len += sprintf(xml_end + xmlend_len, "<devid>%s</devid>\n", devid);
	xmlend_len += sprintf(xml_end + xmlend_len, "</url_preload_response>\n");
	memset(xml_begin, 0, BUF_SIZE_1024);
	xmlbegin_len += sprintf(xml_begin + xmlbegin_len, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	xmlbegin_len += sprintf(xml_begin + xmlbegin_len, "<url_preload_response sessionid=\"%s\">\n", task->sessionid);
	memset(hdr_buffer, 0, BUF_SIZE_4096);
	hdr_len += sprintf(hdr_buffer + hdr_len,"POST http://www.chinacache.com HTTP/1.1\r\n");
	hdr_len += sprintf(hdr_buffer + hdr_len,"Content-Length: %ld\r\n", (long)(task->buflen + xmlbegin_len + xmlend_len));
	hdr_len += sprintf(hdr_buffer + hdr_len,"Connection: close\r\n");
	hdr_len += sprintf(hdr_buffer + hdr_len,"\r\n");
	hdr_len += sprintf(hdr_buffer + hdr_len, "%s", xml_begin);
	total = task->buflen + hdr_len + xmlend_len;
	ptr = realloc(task->buf, total + 1);
	if (NULL == ptr)
	{
		plog(ERROR, "realloc() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	task->buf = ptr;
	memmove(task->buf + hdr_len, task->buf, task->buflen);
	memcpy(task->buf, hdr_buffer, hdr_len);
	memcpy(task->buf + task->buflen + hdr_len, xml_end, xmlend_len);
	task->buf[total - 1] = '\0';
	task->buflen = strlen(task->buf);;

	return 0;
}

static int urlPreload2(task_t *task)
{
	url_node_t *cur = NULL;

	for (cur = task->urls.list; NULL != cur; cur = cur->next)
	{
		urlPreloadOne(task, cur);
	}

	return 0;
}

static int urlPreload(task_t *task)
{
	urlPreload2(task);
	packReportData(task);
	postResultToServer(task);
	preloaderReleaseTask(task);

	return 0;
}

static int preloaderRecvHandle(sockfd)
{
	peek_data_t peek;
	task_t *task = NULL;
	ssize_t ret, size, retry = 5;

	if (FALSE == checkSockfdValid(sockfd))
	{
		plog(ERROR, "socket invalid, SOCKFD=[%d]", sockfd);
		return -1;
	}
	task = preloader.index.idx[sockfd].rfc;
	memset(&peek, 0, tsize.peek_data_t_size);
	if (preloaderPeekMsgHeaders(sockfd, &peek) < 0)
	{
		plog(ERROR, "preloaderPeekMsgHeaders() failed");
		return -1;
	}
	size = peek.hdrlen + peek.contlen;
	task->buffer = calloc(size + BUF_SIZE_128, 1);
	if (NULL == task->buffer)
	{
		plog(ERROR, "calloc() for task.buffer failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	task->recvlen = 0;
	do {
		ret = recv(sockfd, task->buffer + task->recvlen, size, 0);
		if (ret < 0)
		{       
			if (EAGAIN == errno)
				return SUCC;
			if (EINTR == errno && --retry)
			{       
				usleep(1000);
				continue;
			}           
			return -1;
		}
		if (0 == ret)
		{
			if (task->recvlen < size)
				return -1;
			break;
		}
		task->recvlen += ret;
		size -= ret;
		if (0 == size)
			break;
	} while (1);
	detectEpollCall(EPOLL_MOD, sockfd, EPOLLOUT|EPOLLERR|EPOLLHUP, NULL);
	plog(COMMON, "receive data, DATA:\n%s", task->buffer);
	if (preloaderParseMsgBody(task, &peek) < 0)
		return -1;
	preloaderSafeFree((void *)&task->buffer);
	task->recvlen = 0;
	urlPreload(task);
	/*
	   if (preloaderConnectToRefreshd(sockfd) < 0)
	   return -1;
	 */

	return 0;
}

static int preloaderSendHandle(int sockfd)
{
	task_t *task;
	ssize_t ret, sendlen = 0, retry = 5;

	task = preloader.index.idx[sockfd].rfc;
	if (FALSE == checkSockfdValid(sockfd))
	{
		plog(ERROR, "sockfd invalid, SOCKFD=[%d]", sockfd);
		return -1;
	}
	while (sendlen < task->buflen)
	{
		if (retry-- < 0)
		{       
			plog(ERROR, "retry > 5, but have remaining data not be sent");
			return -1;
		}       
		ret = send(sockfd, task->buf + sendlen, task->buflen - sendlen, 0);
		if (ret < 0)
		{       
			if (EINTR == errno && --retry)
			{       
				usleep(1000);
				continue;
			}       
			plog(ERROR, "send() failed, ERROR=[%s]", strerror(errno));
			return -1;
		}       
		sendlen += ret; 
	};

	return 0;
}

static int preloaderEpollWaitHandle(const int type, int sockfd)
{
	switch (type)
	{
		case EPOLL_IN:
			if (sockfd == preloader.listenfd)
				preloaderAcceptHandle(sockfd);
			else
				preloaderRecvHandle(sockfd);
			break;
		case EPOLL_OUT:
			preloaderSendHandle(sockfd);
			break;
		case EPOLL_ERR:
			break;
		case EPOLL_HUP:
			break;
		default:
			break;
	}
	return 0;
}

static void preloaderCheckTimeout(void)
{
	int i, prev = 0;
	time_t curtime, usedtime;

	curtime = time(NULL);
	for (i = 0; i <= preloader.index.max_sockfd; ++i)
	{
		if (1 == preloader.index.idx[i].inuse)
		{
			usedtime = curtime - preloader.index.idx[i].timestamp;
			if (usedtime > PRELOADER_TIMEOUT)
			{
				plog(WARNING, "communication timeout, USEDTIME=[%ld]", usedtime);
				close(i);
				preloader.index.idx[i].inuse = 0;
				preloaderTasklistDel(preloader.index.idx[i].rfc);
				if (i == preloader.index.max_sockfd)
					preloader.index.max_sockfd = prev;
			}
			prev = i;
		}
	}
}

static int preloaderListenLoop(void)
{
	do {    
		usleep(1);
		detectEpollCall(EPOLL_WAIT, 0, 0, preloaderEpollWaitHandle);
		//preloaderCheckTimeout();
	} while (1);
	detectEpollCall(EPOLL_DESTROY, 0, 0, NULL);

	return 0;
}

int preloaderCloseSockfdOnexec(int sockfd)
{
	int flags;
	int dummy = 0;

	if ((flags = fcntl(sockfd, F_GETFL, dummy)) < 0)
		return -1;
	if (fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC) < 0)
		return -1;

	return 0;
}

static int preloaderSetListener(void)
{
	int ret;
	int value = 1;
	struct sockaddr_in local;

	preloader.listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if (preloader.listenfd < 0)
		return -1;
	memset(&local, 0, sizeof(struct sockaddr_in));
	local.sin_family = AF_INET;
	local.sin_port = htons(preloader.port);
	ret = inet_pton(AF_INET, LOCAL_DEFAULT_IP, &local.sin_addr);
	if (0 == ret)
		plog(ERROR, "local ip is invalid, IP=[0.0.0.0]");
	if (0 > ret)
		plog(ERROR, "inet_pton() failed, ERROR=[%s]", strerror(errno));
	if (setsockopt(preloader.listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&value, sizeof(value)) < 0)
		plog(WARNING, "setsockopt(SO_REUSEADDR) failed, ERROR=[%s]", strerror(errno));
	preloaderCloseSockfdOnexec(preloader.listenfd);
	if (bind(preloader.listenfd, (struct sockaddr *)&local, sizeof(local)) < 0)
	{
		plog(ERROR, "bind() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	if(listen(preloader.listenfd, PRELOADER_MAX_EVENTS) < 0)
	{
		plog(ERROR, "listen() failed, ERROR=[%s]", strerror(errno));
		return -1;
	}
	if (setSockfdNonblocking(preloader.listenfd) < 0)
	{
		close(preloader.listenfd);
		return -1;
	}
	detectEpollCall(EPOLL_ADD, preloader.listenfd, EPOLLIN|EPOLLERR|EPOLLHUP, NULL);

	return 0;
}

static void preloaderTryWritePid(void)
{
	time_t tm; 
	int fd, val;
	struct stat st;
	struct flock lock;
	char buf[BUF_SIZE_256];

	if (stat(PRELOADER_PID_DIR, &st) < 0)
	{
		if (mkdir(PRELOADER_PID_DIR, S_IRWXU) < 0)
		{       
			fprintf(stderr, "mkdir() failed, DIR=[%s], ERROR=[%s]\n",
					PRELOADER_PID_DIR, strerror(errno));
			goto EXIT;
		}           
	}
	fd = open(PRELOADER_PID_FILE, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
	if(fd < 0)
	{
		plog(WARNING, "open(pidfile) failed, ERROR=[%s]", strerror(errno));
		goto EXIT; 
	}
	//set file write lock
	lock.l_len = 0;
	lock.l_start = 0;
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	if (fcntl(fd, F_SETLK, &lock) < 0)
	{
		fprintf(stderr, "Already have one preloader instance running!\n");
		plog(WARNING, "Already have one preloader instance running");
		goto EXIT;
	}
	if(ftruncate(fd, 0) < 0)
	{
		plog(WARNING, "truncate(pidfile) failed, ERROR=[%s]", strerror(errno));
		goto EXIT;
	}
	tm = time(NULL);
	memset(buf, 0, BUF_SIZE_256);
	sprintf(buf, "%d\n%ld\n", getpid(), tm);
	if(write(fd, buf, strlen(buf)) != strlen(buf))
	{
		plog(WARNING, "write(pidfile) failed, ERROR=[%s]", strerror(errno));
		goto EXIT;
	}
	val = fcntl(fd, F_GETFD, 0);
	if(val < 0)
	{
		plog(WARNING, "fcntl(F_GETFD) failed, ERROR=[%s]", strerror(errno));
		goto EXIT;
	}
	//set fd flag: close-on-exec
	val |= FD_CLOEXEC;
	if(fcntl(fd, F_SETFD, val) < 0)
	{
		plog(WARNING, "fcntl(F_SETFD) failed, ERROR=[%s]", strerror(errno));
		goto EXIT;
	}
	puts("try lock succeed");
	return;

EXIT:
	puts("try lock failed");
	exit(1);
}

void preloaderSetSelfDaemon(void)
{
	int fd;
	pid_t pid;

	fflush(NULL);
	pid = fork(); 
	if(pid < 0)
	{ 
		fprintf(stderr, "fork() for daemon failed, ERROR=[%s]", strerror(errno));
		exit(1);
	}
	if(pid > 0)
		exit(0);
	fd = open("/dev/null", O_RDWR);
	if (fd < 0)
	{	
		perror("open()");	
		fclose(stdin);
		fclose(stdout);
		fclose(stderr);
	}
	else
	{	
		dup2(fd, 0);	
		dup2(fd, 1);
		dup2(fd, 2);	
		if (fd>2) close(fd);
	}
	umask(0);
	chdir("/");
	setsid();
}

int main(int argc, char **argv)
{
	preloaderParseOptions(argc, argv);
	preloaderSetSelfDaemon();
	preloaderTryWritePid();
	preloaderResetRlimit();
	preloaderVarInit();
	openLogFile();
	preloaderKillRunningPort(preloader.port);
	checkDirectory(store_dir);
	getDevId();
	if (preloaderSetListener() < 0)
		exit(1);
	preloaderListenLoop();

	return 0;
}

